﻿/* Copyright 2008-10 Jarrett Vance
 * see http://www.codeplex.com/blogsvc/license */
namespace AtomSite.WebCore
{
  using System;
  using System.IO;
  using System.Net;
  using System.Web;
  using System.Web.Mvc;
  using System.Xml;
  using System.Xml.Linq;
  using AtomSite.Domain;

  public class AtomPubController : BaseController
  {
    protected IAtomPubService AtomPubService;
    public AtomPubController(IAtomPubService atompub)
      : base()
    {
      AtomPubService = atompub;
    }

    public ViewResult Error()
    {
      Response.StatusCode = (int)HttpStatusCode.InternalServerError;
      return View("Error", new ErrorModel(Server.GetLastError()));
    }
    public ViewResult NotFound()
    {
      Response.StatusCode = (int)HttpStatusCode.NotFound;
      return View("NotFound", new PageModel());
    }

    public ActionResult ServiceIndex()
    {
      FeedModel model = new FeedModel();
      model.Feed = AtomPubService.GetFeed(0, 10);
      return View("AtomPubIndex", model);
    }

    public ActionResult WorkspaceIndex(string workspace)
    {
      FeedModel model = new FeedModel();
      model.Feed = AtomPubService.GetFeed(workspace, 0, 10);
      return View("AtomPubIndex", model);
    }

    public ActionResult CollectionIndex(string workspace, string collection)
    {
      FeedModel model = new FeedModel();
      model.Feed = AtomPubService.GetFeed(Collection.Id, 0, 10);
      return View("AtomPubIndex", model);
    }

    public ActionResult Resource(string workspace, string collection)
    {
      EntryModel model = new EntryModel()
      {
        Entry = AtomPubService.GetEntry(EntryId)
      };
      return View("AtomPubResource", model);
    }

    [AcceptVerbs(HttpVerbs.Get), ActionName("Service")]
    public ActionResult GetService()
    {
      try
      {
        AppService s = AtomPubService.GetService();
        return new XmlWriterResult((w) => s.Xml.WriteTo(w))
        {
          ContentType = Atom.ContentTypeService
        };
      }
      catch (Exception ex)
      {
        return GetErrorResult(ex);
      }
    }

    [AcceptVerbs(HttpVerbs.Get), ActionName("Categories")]
    public ActionResult GetCategories(string workspace, string collection, string scheme)
    {
      //TODO: need support in AtomPubService
      throw new NotImplementedException();
    }

    [AcceptVerbs(HttpVerbs.Get), ActionName("Collection")]
    public ActionResult GetCollection(string workspace, string collection, int? page)
    {
      try
      {
        int pageIndex = (page ?? 1) - 1;
        AtomFeed feed = AtomPubService.GetCollectionFeed(Collection.Id, pageIndex);
        return new XmlWriterResult((w) => feed.Xml.WriteTo(w))
        {
          ContentType = Atom.ContentTypeFeed
        };
      }
      catch (Exception ex)
      {
        return GetErrorResult(ex);
      }
    }

    /// <summary>
    /// Post an atom entry or media resource to a collection.
    /// </summary>
    /// <param name="collection"></param>
    /// <returns></returns>
    [AcceptVerbs(HttpVerbs.Post), ActionName("Collection")]
    public ActionResult PostCollection(string workspace, string collection)
    {
      try
      {
        if (HttpContext.Request.ContentType.ToUpper().StartsWith(Atom.ContentType.ToUpper()))
        {
          AtomEntry entry = new AtomEntry();
          //note: don't dispose of incoming stream or it will close the response, still true?
          XmlReader reader = new XmlTextReader(HttpContext.Request.InputStream);
          entry.Xml = XElement.Load(reader);
          entry = AtomPubService.CreateEntry(Collection.Id, entry, GetSlug());
          return new XmlWriterResult((w) => entry.Xml.WriteTo(w))
          {
            StatusCode = HttpStatusCode.Created,
            ContentType = Atom.ContentTypeEntry,
            ETag = AtomPubService.GetEntryEtag(entry.Id),
            Location = entry.Location,
            ContentLocation = entry.Location
          };
        }
        else //not atom so must be media
        {
          AtomEntry mediaLinkEntry = AtomPubService.CreateMedia(Collection.Id, HttpContext.Request.InputStream,
            GetSlug(), HttpContext.Request.ContentType);
          string contentType;
          return new XmlWriterResult((w) => mediaLinkEntry.Xml.WriteTo(w))
          {
            StatusCode = HttpStatusCode.Created,
            ContentType = Atom.ContentTypeEntry,
            ETag = AtomPubService.GetMediaEtag(mediaLinkEntry.Id, out contentType),
            Location = mediaLinkEntry.Location,
            ContentLocation = mediaLinkEntry.Location
          };
        }
      }
      catch (Exception ex)
      {
        return GetErrorResult(ex);
      }
    }

    [AcceptVerbs(HttpVerbs.Get), ActionName("Entry")]
    public ActionResult GetEntry(string workspace, string collection, int? year,
      int? month, int? day, string path)
    {
      try
      {
        Id id = Collection.Id.AddPath(year, month, day, path);
        string etag = AtomPubService.GetEntryEtag(id);
        if (NoneMatch(etag)) //only get data when user needs it
        {
          AtomEntry entry = AtomPubService.GetEntry(id);
          return new XmlWriterResult((w) => entry.Xml.WriteTo(w))
          {
            ContentType = Atom.ContentTypeEntry,
            ETag = AtomPubService.GetEntryEtag(id),
          };
        }
        else
        {
          return new HttpResult() { StatusCode = HttpStatusCode.NotModified };
        }
      }
      catch (Exception ex)
      {
        return GetErrorResult(ex);
      }
    }

    [AcceptVerbs(HttpVerbs.Put), ActionName("Entry")]
    public ActionResult PutEntry(string workspace, string collection, int? year,
      int? month, int? day, string path)
    {
      try
      {
        if (!HttpContext.Request.ContentType.ToUpper().StartsWith(Atom.ContentType.ToUpper()))
          throw new InvalidContentTypeException(HttpContext.Request.ContentType);

        Id id = Collection.Id.AddPath(year, month, day, path);
        string etag = AtomPubService.GetEntryEtag(id);

        if (Match(etag)) //only update when user had latest data
        {
          AtomEntry entry = new AtomEntry();
          //note: don't dispose of incoming stream or it will close the response, still true?
          XmlReader reader = new XmlTextReader(HttpContext.Request.InputStream);
          entry.Xml = XElement.Load(reader);
          entry = AtomPubService.UpdateEntry(id, entry, GetSlug());

          return new XmlWriterResult((w) => entry.Xml.WriteTo(w))
          {
            ContentType = Atom.ContentTypeEntry,
            ETag = AtomPubService.GetEntryEtag(id)
            //locations?
          };
        }
        else
        {
          return new HttpResult() { StatusCode = HttpStatusCode.PreconditionFailed };
        }
      }
      catch (Exception ex)
      {
        return GetErrorResult(ex);
      }
    }

    [AcceptVerbs(HttpVerbs.Delete), ActionName("Entry")]
    public ActionResult DeleteEntry(string workspace, string collection, int? year,
      int? month, int? day, string path)
    {
      try
      {
        Id id = Collection.Id.AddPath(year, month, day, path);
        string etag = AtomPubService.GetEntryEtag(id);
        if (Match(etag))
        {
          AtomPubService.DeleteEntry(id);
          return new EmptyResult();
        }
        else
        {
          return new HttpResult() { StatusCode = HttpStatusCode.PreconditionFailed };
        }
      }
      catch (Exception ex)
      {
        return GetErrorResult(ex);
      }
    }

    [AcceptVerbs(HttpVerbs.Get), ActionName("Media")]
    public ActionResult GetMedia(string workspace, string collection, int? year,
      int? month, int? day, string path)
    {
      try
      {
        Id id = Collection.Id.AddPath(year, month, day, path);
        string contentType;
        string etag = AtomPubService.GetMediaEtag(id, out contentType);

        if (NoneMatch(etag)) //only get data when user needs it
        {
          Stream stream = AtomPubService.GetMedia(id, out contentType);
          return new StreamResult()
          {
            Stream = stream,
            ContentType = contentType,
            ETag = etag
          };
        }
        else
        {
          return new HttpResult() { StatusCode = HttpStatusCode.NotModified };
        }
      }
      catch (Exception ex)
      {
        return GetErrorResult(ex);
      }
    }

    [AcceptVerbs(HttpVerbs.Put), ActionName("Media")]
    public ActionResult PutMedia(string workspace, string collection, int? year,
      int? month, int? day, string path)
    {
      try
      {
        Id id = Collection.Id.AddPath(year, month, day, path);
        string contentType;
        string etag = AtomPubService.GetMediaEtag(id, out contentType);

        if (Match(etag)) //only update when user had latest data
        {
          AtomEntry entry = AtomPubService.UpdateMedia(id, HttpContext.Request.InputStream,
            HttpContext.Request.ContentType);
          return new EmptyResult();
        }
        else
        {
          return new HttpResult() { StatusCode = HttpStatusCode.PreconditionFailed };
        }
      }
      catch (Exception ex)
      {
        return GetErrorResult(ex);
      }
    }

    [AcceptVerbs(HttpVerbs.Delete), ActionName("Media")]
    public ActionResult DeleteMedia(string workspace, string collection, int? year,
      int? month, int? day, string path)
    {
      try
      {
        Id id = Collection.Id.AddPath(year, month, day, path);
        string contentType;
        string etag = AtomPubService.GetMediaEtag(id, out contentType);
        if (Match(etag))
        {
          AtomPubService.DeleteMedia(id);
          return new EmptyResult();
        }
        else
        {
          return new HttpResult() { StatusCode = HttpStatusCode.PreconditionFailed };
        }
      }
      catch (Exception ex)
      {
        return GetErrorResult(ex);
      }
    }

    [AcceptVerbs(HttpVerbs.Head), ActionName("Entry")]
    public ActionResult HeadEntry(string workspace, string collection, int? year,
      int? month, int? day, string path)
    {
      try
      {
        string etag = AtomPubService.GetEntryEtag(
          Collection.Id.AddPath(year, month, day, path));
        HttpResult result = new HttpResult() { ContentType = Atom.ContentTypeEntry };
        result.Headers["ETag"] = etag;
        return result;
      }
      catch (Exception ex)
      {
        return GetErrorResult(ex);
      }
    }

    [AcceptVerbs(HttpVerbs.Head), ActionName("Media")]
    public ActionResult HeadMedia(string workspace, string collection, int? year,
      int? month, int? day, string path)
    {
      try
      {
        string contentType;
        string etag = AtomPubService.GetMediaEtag(Collection.Id.AddPath(year, month, day, path),
          out contentType);
        HttpResult result = new HttpResult() { ContentType = contentType };
        result.Headers["ETag"] = etag;
        return result;
      }
      catch (Exception ex)
      {
        return GetErrorResult(ex);
      }
    }

    [AcceptVerbs(HttpVerbs.Put), ActionName("Service")]
    public ActionResult PutService()
    {
      throw new NotImplementedException();
    }

    [AcceptVerbs(HttpVerbs.Get), ActionName("Feed")]
    public ActionResult GetFeed(string workspace, string collection, string type, int? page)
    {
      try
      {
        if (!Collection.SyndicationOn) throw new ResourceNotFoundException(type, "feed");
        
        int pageIndex = (page ?? 1) - 1;
        AtomFeed feed = AtomPubService.GetFeed(Collection.Id, pageIndex, 10);

        //if extended entries are on then only show content before split
        if (Collection.ExtendedEntriesOn)
        {
          foreach (AtomEntry e in feed.Entries)
          {
            e.Content = e.IsExtended ? e.ContentBeforeSplit : e.Content;
          }
        }

        return GetFeedResult(feed, type);
      }
      catch (Exception ex)
      {
        return GetErrorResult(ex);
      }
    }

    //public void GenerateLinks(Func<string, Id, RouteValueDictionary, Uri> routeUri, string atomRouteName, string webRouteName, int pageIndex, int pageSize, bool addPaging)
    //{
    //  List<AtomLink> links = new List<AtomLink>();
    //  RouteValueDictionary routeData = new RouteValueDictionary();
    //  if (addPaging) routeData.Add("page", 1);
    //  if (!string.IsNullOrEmpty(atomRouteName))
    //  {
    //    links.Add(new AtomLink { Href = routeUri(atomRouteName, Id, routeData), Rel = "self", Type = Atom.ContentType });
    //    if (addPaging) links.AddRange(GetPagingLinks(routeUri, atomRouteName, pageIndex, pageSize, Atom.ContentType));
    //  }
    //  if (!string.IsNullOrEmpty(webRouteName))
    //  {
    //    links.Add(new AtomLink { Href = routeUri(webRouteName, Id, routeData), Rel = "alternate", Type = "text/html" });
    //    if (addPaging) links.AddRange(GetPagingLinks(routeUri, webRouteName, pageIndex, pageSize, "text/html"));
    //  }
    //  ////add windows live writer link
    //  //links.Add(new AtomLink()
    //  //{
    //  //  Href = routeUri("BlogWriterManifest", Id, null),
    //  //  Rel = "wlwmanifest",
    //  //  Type = "application/wlwmanifest+xml"
    //  //});
    //  Links = links;
    //}
  }
}
